<template>
  <div class="wrapper" @scroll="onScrollVal">
    <div :style="globalStyle">
      <div class="h-style" ref="layer" :style="cptedStyle">
        <item-view
          v-for="lv in list"
          :key="lv.id"
          :dataForSlot="lv"
          class="ln-style"
          @update-height="updateSlotVal"
        >
          <template v-slot="slotProps">
            <div class="title">
              <span>No.{{ slotProps.data.id }}</span> -
              <span>{{ slotProps.data.words }}</span>
            </div>
          </template>
        </item-view>
      </div>
    </div>
  </div>
</template>

<script setup>
import * as utils from "@/utils/index.js";
import ItemView from "./virtual-item.vue";
import { computed, nextTick, onUpdated } from "vue";

let bufferVal = 5,
  all = 100,
  visibale = 20,
  dynamic = true;

const rangeAveSize = ref(80);
const layer = ref(null);
const stVal = ref(0);
const translateY = ref(0);
const scrollTop = ref(0);
const hl = ref(utils.listCreate(all));
const currentOffset = ref(0);

const cachedVal = new Map();
const indexToIdCache = [];
for (const item of hl.value) {
  indexToIdCache.push(item.id);
}

const cptedStyle = computed(() => {
  const tVal = Math.max(translateY.value, 0);
  return { transform: `translateY(${tVal}px)` };
});

const globalStyle = computed(() => {
  const hgVal = rangeAveSize.value * all;
  return { height: `${hgVal}px` };
});

const list = computed(() => {
  const val = Math.max(stVal.value, 0);
  const end = stVal.value + visibale;
  const finVal = Math.min(end, all);
  return hl.value.slice(val, finVal);
});

onMounted(() => {
  const el = layer.value;
  const rect = el.getBoundingClientRect();
  console.log("BoundingClientRect", rect);
});

const getPadFront = (start) => {
  console.log("offset", currentOffset.value);
  if (dynamic) {
    translateY.value = getIndexOffset(start);
  } else {
    translateY.value = start * rangeAveSize.value;
  }
};

const onScrollVal = (e) => {
  const scrollVal = e.target.scrollTop;
  const isUpScroll = scrollTop.value - scrollVal > 0;
  scrollTop.value = scrollVal;
  let startVal = ~~(scrollVal / rangeAveSize.value);
  // Key points of logical code
  if (dynamic) startVal = getScrollOvers(scrollVal);

  if (isUpScroll) {
    // Key points of logical code
    // bufferVal is a value that can be dynamically adjusted as needed
    if (startVal < stVal.value) {
      stVal.value = startVal - bufferVal;
      getPadFront(stVal.value);
      console.log("----", startVal, scrollVal);
    }
  } else {
    // Key points of logical code
    // Synchronize control of container height and translateY value through throttling
    if (startVal >= stVal.value + bufferVal) {
      stVal.value = startVal;
      getPadFront(stVal.value);
    }
    console.log("startVal", startVal, scrollVal);
  }
};

const computedAveSize = () => {
  const h = cachedVal.values();
  const t = h.reduce((t, v) => t + v, 0);
  return t / cachedVal.size;
};

const updateSlotVal = (data) => {
  const prev = cachedVal.get(data.id);
  if (prev === undefined) {
    cachedVal.set(data.id, data.height);
    rangeAveSize.value = computedAveSize();
  }
};

onUpdated(() => {
  console.log(cachedVal);
});

const getEstimateSize = () => {
  return rangeAveSize.value;
};

const getIndexOffset = (cptedVal = 0) => {
  let offset = 0;
  let indexSize = 0;
  for (let index = 0; index < cptedVal; index++) {
    const key = indexToIdCache[index];
    indexSize = cachedVal.get(key);
    offset += isNaN(indexSize) ? getEstimateSize() : indexSize;
  }

  currentOffset.value = offset;

  return offset;
};

const getScrollOvers = (offset) => {
  if (offset <= 0) return 0;
  // console.log(cachedVal);

  let low = 0;
  let middle = 0;
  let middleOffset = 0;
  let high = all;

  while (low <= high) {
    middle = low + ~~((high - low) / 2);
    middleOffset = getIndexOffset(middle);
    // console.log('low', low, offset);
    // console.log("middle", middle, middleOffset);

    if (middleOffset === offset) {
      return middle;
    } else if (middleOffset < offset) {
      low = middle + 1;
    } else if (middleOffset > offset) {
      high = middle - 1;
    }
  }
  return low > 0 ? --low : 0;
};
</script>

<style lang="scss" scoped>
.wrapper {
  box-sizing: content-box;
  height: 480px;
  overflow: auto;
  overflow-anchor: none;
  border: 2px solid yellowgreen;
}

.ln-style {
  line-height: 40px;
}
</style>
